package com.piece.core.jpa.config;

import com.piece.core.db.context.DynamicDataSource;
import com.piece.core.jpa.listener.BeanPostLogListener;
import com.piece.core.jpa.properties.PersistenceProperties;
import com.piece.core.log.client.LogFactory;
import com.piece.core.log.properties.LogProperties;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.annotation.Resource;
import javax.persistence.EntityManagerFactory;
import java.util.Map;

@Configuration
@DependsOn("dynamicDataSource")
@ConditionalOnClass(DynamicDataSource.class)
@EnableConfigurationProperties({
        JpaProperties.class,
        PersistenceProperties.class
})
@EnableJpaRepositories(repositoryImplementationPostfix = "RepositoryImpl",
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
public class EntityManageAutoConfig {

    @Resource
    @Qualifier("dynamicDataSource")
    private DynamicDataSource dynamicDataSource;

    @Autowired
    private LogProperties logProperties;

    @Autowired
    private LogFactory logFactory;

    /**
     * 配置EntityManager工厂
     */
    @Primary
    @Bean(name = "entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(JpaProperties jpaProperties, PersistenceProperties persistenceProperties) {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(persistenceProperties.getBasePackage().stream().toArray(String[]::new));
        // 动态数据源
        factory.setDataSource(this.dynamicDataSource);
        Map<String, String> properties = jpaProperties.getProperties();
        properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        factory.setJpaPropertyMap(properties);
        // 在完成了其它所有相关的配置加载以及属性设置后,才初始化
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    /**
     * 配置事物管理器
     */
    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager writeTransactionManager(JpaProperties jpaProperties, PersistenceProperties persistenceProperties) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory(jpaProperties, persistenceProperties));
        jpaTransactionManager.setDataSource(this.dynamicDataSource);
        return jpaTransactionManager;
    }

    @Bean(name = "txAdvice")
    public TransactionInterceptor txAdvice(PlatformTransactionManager transactionManager) {
        DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
        txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        txAttr_REQUIRED_READONLY.setReadOnly(true);
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.addTransactionalMethod("add*", txAttr_REQUIRED);
        source.addTransactionalMethod("save*", txAttr_REQUIRED);
        source.addTransactionalMethod("insert*", txAttr_REQUIRED);
        source.addTransactionalMethod("update*", txAttr_REQUIRED);
        source.addTransactionalMethod("delete*", txAttr_REQUIRED);
        source.addTransactionalMethod("remove*", txAttr_REQUIRED);
        source.addTransactionalMethod("exec*", txAttr_REQUIRED);
        source.addTransactionalMethod("batch*", txAttr_REQUIRED);
        source.addTransactionalMethod("init*", txAttr_REQUIRED);
        source.addTransactionalMethod("reset*", txAttr_REQUIRED);
        source.addTransactionalMethod("bind*", txAttr_REQUIRED);
        source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("entity*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public BeanNameAutoProxyCreator txProxy() {
        BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
        creator.setInterceptorNames("txAdvice");
        creator.setBeanNames("*Service");
        creator.setProxyTargetClass(true);
        return creator;
    }

//    @Bean
//    public BeanPostLogListener beanModifyLogListener() {
//        return new BeanPostLogListener(logProperties, logFactory);
//    }
}
